/*
 * @(#)Similarity.java
 *
 * (c) COPYRIGHT 1998-1999 MOTOROLA INC.
 * MOTOROLA CONFIDENTIAL PROPIETARY
 * MOTOROLA Advanced Technology and Software Operations
 *
 * REVISION HISTORY:
 * Author Date       CR Number         Brief Description
 * ------ ---------- ----------------- ------------------------------
 * wfo007 22/08/2008
 */

package br.edu.ufcg.splab.techniques.reduction.strategies;

import java.util.ArrayList;
import java.util.List;

import br.edu.ufcg.splab.generator.TestCase;
import br.edu.ufcg.splab.generator.TestSuite;
import br.edu.ufcg.splab.techniques.reduction.InterfaceReduction;
import br.edu.ufcg.splab.techniques.reduction.functions.choice.ChoiceFunction;
import br.edu.ufcg.splab.techniques.reduction.functions.similarity.DistanceFunction;
import br.edu.ufcg.splab.techniques.reduction.requirement.InterfaceRequirement;
import br.edu.ufcg.splab.techniques.reduction.requirement.RequirementTracer;


/**
 * A class that 
 * <pre>
 * 
 * CLASS:
 * 	
 *
 * RESPONSIBILITIES:
 * 	
 *
 * COLABORATORS: 
 *	
 *
 * USAGE:
 *	
 *
 * </pre>
 **/
public class ReductionSimilarity  implements InterfaceReduction{
	
	private DistanceFunction function;
	private SimilarityMatrix similarityMatrix;
	private ChoiceFunction choice;

	/**
	 * The constructor of a Reduction Similarity Technique.
	 * The test suite will be generated in this constructor. 
	 * @param function The similarity function of the test cases.
	 */
	public ReductionSimilarity(DistanceFunction function, ChoiceFunction choice)	{
		this.function = function;
		this.choice = choice;
		this.similarityMatrix = new SimilarityMatrix();
	}
	
	public TestSuite reduceTestSuite(TestSuite suite, List<RequirementTracer> tracer) {			
		return this.removeSimilarPaths(suite, tracer);
	} 

	public DistanceFunction getDistanceFunction(){
		return function;
	}
	
	public void setDistanceFunction(DistanceFunction function){
		this.function = function;
	}
		
    public ChoiceFunction getChoiceFunction() {
		return choice;
	}

	public void setChoiceFunction(ChoiceFunction choice) {
		this.choice = choice;
	}

	/**
	 * Remove the paths with high similarity while the test requirements are covered.
	 * 
	 * @param suite The complete test suite.
	 * @param tracers The ordered list of requirements of the complete test suite.
	 * @return The reduced test suite.
	 */
	protected TestSuite removeSimilarPaths(TestSuite suite, List<RequirementTracer> tracers) {
		List<TestCase> reducedTests = new ArrayList<TestCase>();		
		List<TestCase> tcs = new ArrayList<TestCase>();	

		for(RequirementTracer tracer: tracers){
			reducedTests.addAll(tcs);
			boolean covering = true;
			
			tcs = new ArrayList<TestCase>();
			
			TestSuite newTS = new TestSuite();
			for(TestCase tc: suite.getTestSuite()){
				if(!reducedTests.contains(tc)){
					tcs.add(tc);
				}
			}
			
			newTS.addAll(tcs);
			
			double[][] matrix = similarityMatrix.createMatrix(newTS, function);
				
			RequirementTracer req = tracer;
			
			do {
				double max = -1.0;
				int indexLine = 0;
				int indexColumn = 0;
				// We use Matrix.size()-1: since the matrix is symmetrical,
				// the last line doesn't need to be verified.
				for (int i = 0; i < matrix.length; i++) {
					for (int j = i + 1; j < matrix.length; j++) {
						double similarity = matrix[i][j];
						if (similarity > max) {
							max = similarity;
							indexLine = i;
							indexColumn = j;					
						}else if(similarity == max && Math.random() < 0.5) {
							//Handles a tie between the similarity values of a matrix.
							max = similarity;
							indexLine = i;
							indexColumn = j;						
						}
					}
				}
				
				//all the values on the matrix is already analyzed.
				if(max == -1.0)	{
				    covering = false;
				    continue;
				}		
				
				// Remove the shortest path
				int firstChoose = 0;
				int secondChoose = 0;
				
				choice.defineOrderChoice(tcs.get(indexLine), tcs.get(indexColumn));
				if(choice.firstChoice().equals(tcs.get(indexLine))){
					firstChoose = indexLine;
			    	secondChoose = indexColumn;
			    }else {
			    	firstChoose = indexColumn;
			    	secondChoose = indexLine;
			    }
																		
				// remove the index "firstChoose" of the testsuite
				TestCase removed = tcs.remove(firstChoose);
				List<TestCase> listTemp = new ArrayList<TestCase>();
				listTemp.addAll(reducedTests);
				listTemp.addAll(tcs);
				if(satisfyRequirements(listTemp, req)) {
			        //Remove the line and the column of the matrix
		            matrix = similarityMatrix.removedLineAndColumn(matrix, firstChoose);  		            
				}else{
				    tcs.add(firstChoose,removed);
				    TestCase secondRemoved = tcs.remove(secondChoose);
				    listTemp = new ArrayList<TestCase>();
					listTemp.addAll(reducedTests);
					listTemp.addAll(tcs);
				    if(satisfyRequirements(listTemp, req)) {
			            //Remove the line and the column of the matrix
		                matrix = similarityMatrix.removedLineAndColumn(matrix, secondChoose);		 
				    }else{
				        tcs.add(secondChoose, secondRemoved);
				        matrix[indexLine][indexColumn] = -1;				        
				    }
				}	
			} while (covering );		
		}
		
		reducedTests.addAll(tcs);
		
		List<TestCase> newTCs = orderTestSuite(reducedTests);
		TestSuite newTS = new TestSuite();
		newTS.addAll(newTCs);
		
		return newTS;	
	}
		
	
	/**
	 * Order the test suite from the similarity degrees.
	 * @param tcs The test cases of the reduced test suite to ordination.
	 * @return The test cases ordered.
	 */
	private List<TestCase> orderTestSuite(List<TestCase> tcs){
		TestSuite testSuite = new TestSuite();
		testSuite.addAll(tcs);
		double[][] matrix = similarityMatrix.createMatrix(testSuite, function);
		
		List<Double> sum_similarity_line = new ArrayList<Double>();
		for (int j = 0; j < matrix.length; j++){
			double sum_similarity = 0.00;
			for (int i = 0; i < matrix.length; i++){
				sum_similarity = matrix[i][j] + sum_similarity;
			}
			sum_similarity_line.add(sum_similarity);
		}
		
		List<Double> sum_similarity_column = new ArrayList<Double>();
		for (int i = 0; i < matrix.length; i++){
			double sum_similarity = 0.00;
			for (int j = 0; j < matrix.length; j++){
				sum_similarity = matrix[i][j] + sum_similarity;
			}
			sum_similarity_column.add(sum_similarity);
		}
		
		List<Double> sum_similarity_tcs = new ArrayList<Double>();
		for (int j = 0; j < sum_similarity_column.size(); j++){
			double x = sum_similarity_column.get(j);
			double y = sum_similarity_line.get(j);
			sum_similarity_tcs.add(x + y);		
		}
		
		List<TestCase> newTestCases = new ArrayList<TestCase>();
		
		while(!sum_similarity_tcs.isEmpty()){
			double min = sum_similarity_tcs.get(0);
			int pos_min = 0;
			for (int j = 1; j < sum_similarity_tcs.size(); j++){
				if(min > sum_similarity_tcs.get(j)){
					min = sum_similarity_tcs.get(j);
					pos_min = j;
				}else if(min == sum_similarity_tcs.get(j) && Math.random() < 0.5){
					min = sum_similarity_tcs.get(j);
					pos_min = j;
				}		
			}
			sum_similarity_tcs.remove(pos_min);		
			newTestCases.add(tcs.remove(pos_min));		
		}
		tcs.addAll(newTestCases);
		return tcs;
	}
	
	/**
	 * Verify if all requirements are being satisfied by the given list of test cases.
	 * @param testCases The list of test case to be verified.
	 * @return A boolean value indicating if the given list of test cases are covering 100 
	 * percent of the LTS.
	 */ 
	
	private boolean satisfyRequirements(List<TestCase> testCases, RequirementTracer tracer) {
		boolean requirementCovered = false;			
		for (InterfaceRequirement requirement : tracer.getRequirements()) {	
			requirementCovered = false;			
			for(TestCase testCase: testCases){
				if(tracer.getTestCasesThatSatisfy(requirement) != null){
					if(tracer.getTestCasesThatSatisfy(requirement).contains(testCase)) {
						requirementCovered = true;
					}
				}
			}
			if(!requirementCovered){
				return false;
			}
		}		
		return true;
	}
	
    /**
     * Provide a string representation of the technique to be written.
     * @return The string representation of the technique.
     */
    public String toString() {
    	return "Sim_" + function.getFunctionName();
    }
   
}
